Chapter 28
TAPE and MFC

by Keith McIntyre

In This Chapter

  Overview 988
  History of TAPI 995
  Using Assisted Telephony 999
  Using Basic Telephony 1002

TAPI is an abbreviation for Telephony Application Programming Interface. As the name implies, TAPI enables developers to write applications that take advantage of services provided by telephony vendors. The services can be good ol’ Ma Bell services accessed over an analog modem or advanced telephony services provided by a proprietary Private Branch Exchange (PBX).

TAPI is one of the services defined by the Windows Open System Architecture (WOSA). It is a well-thought-out client interface and internal architecture that has proven to be powerful and extensible. As depicted in Figure 28.1, the TAPI architecture consists of TAPI-enabled applications and TAPI Service Providers. The TAPI-enabled applications talk to the Service Providers indirectly via the Telephony API. TAPI provides an abstraction that allows the applications to be written in a platform-independent fashion. The Service Providers are responsible for taking the platform-independent abstraction and implementing specializations (drivers) that control specific hardware devices.

Figure 28.1 provides a general description of the TAPI architecture. (Different versions of TAPI implement differing architectures underneath the covers. In particular, versions 1.4, 2, and 3 introduce drastic departures from previous versions.)


Figure 28.1  An abstraction of the TAPI architecture.

In this chapter, you’ll start by looking at an overview of TAPI. Next you’ll take a look at the history of TAPI and a peek at what the future of TAPI will offer. You’ll then use both Assisted Telephony and Basic Telephony Services to write sample applications that allow an analog modem to dial an interactive voice call.

Overview

TAPI, the Telephony Application Programming Interface, enables a programmer to develop applications that interface with telephony systems ranging from a simple Plain Old Telephone Service (POTS) line to a modern Private Branch Exchange (PBX). TAPI can be incorporated into an application as an ancillary function. TAPI could, for example, enable an application to dial a phone number. Or an application can be written that is very TAPI-centric. Perhaps the application provides an interface to a PBX system whereby the user can store commonly dialed numbers, record greetings, page colleagues, and take dictation.

Four levels of service are provided by TAPI:

  Assisted Telephony
  Basic Telephony Service
  Supplemental Telephony Service
  Extended Telephony Service

Assisted Telephony

Assisted Telephony provides a short list of functions that allow non-telephony-centric applications to easily add the ability to dial outgoing calls to their feature set. Using Assisted Telephony in conjunction with VBA, one can add dialing capabilities into Microsoft Word documents or Excel spreadsheets. Assisted Telephony can also be used to add phone dialing to Visual C++/MFC applications you write with MSVC 6. Assisted Telephony only supports dialing numbers for interactive voice calls.

Currently, Assisted Telephony supports only two function calls:

  TapiRequestMakeCall()
  TapiGetLocationInfo()

TapiRequestMakeCall() works together with the Dialer.exe application provided with Windows 95 and Windows 98 to handle all the details of finding the right device on which to place the call, dialing the number, and providing a user interface from which the user can hang up the call.

TapiGetLocationInfo() provides a means of obtaining country and city code information that can be used when constructing the phone number given to TapiRequestMakeCall().

Previous versions of Assisted Telephony supported two additional functions:

  TapiRequestMediaCall()
  TapiDropRequest()

These are no longer supported by Win32 applications and should not be used.

Basic Telephony Service

Basic Telephony Services is targeted at programmers who want to have increased control over the telephony operations their application provides but do not have to control advanced PBX functions.

The Basic Telephony Services are deliverable by all Service Providers regardless of the hardware employed. The abstraction provided by TAPI allows applications to be written that will run on different vendors’ hardware platforms. In a similar fashion to the way GDI made platform independent graphics possible, TAPI makes hardware-independent telephony software possible. (And as you will see later, TAPI provides an API negotiation mechanism that allows applications to run against different versions of TAPI as well.)

The services made available through the Basic Telephony Services include the following:

  Address translation
  Making calls
  Answering calls
  Dropping calls
  Monitoring call states and events
  Call handle manipulation

Address translation is concerned with generating a locale-specific version of a canonical address. A canonical address contains all the information required to uniquely identify an endpoint. This includes country code, area code, and the phone number. Canonical addresses start with a + character. Hence, “+1 (619) 554-1400” would be a good representation of the telephone number for reaching Stellcom Incorporated located in San Diego, California (area code 619), USA (country code 1), with the phone number 554-1400.

The address translation process takes into account the current location the call is being placed from. The Modem applet contained in the Control Panel provides a Dialing Properties dialog. A tab in this dialog is titled My Locations. The associated user interface allows the user to establish a number of calling profiles each with independently specified settings for the current area code, country code, access number for dialing long distance or obtaining an outside line, and disabling call waiting. One can also specify a calling card number that should be used when placing long distance calls as well as specifying rules, based on area codes, as to when a call should be treated as long distance. Figure 28.2 shows the Dialing Properties dialog available with Windows 98.


Figure 28.2  The Dialing Properties dialog box.

Address translation takes a canonical address as input, applies the rules specified through the current I Am Dialing From setting in the Dialing Properties dialog, and generates a locale-specific dial string. The locale-specific string includes all the information such as the digit required to get to an outside line, the calling card number to which the call should be called, the area code, and so on. So the canonical number presented earlier might translate to something like “T 9 5541400.”

When you have a locale-specific dial string, you probably want to use it to place a call. Making calls is not a simple task when using Basic Telephony Service. As you will see later in the chapter, a significant amount of code is required to initialize TAPI, obtain a line handle that meets the required communications needs, negotiate a TAPI service level, and establish a call.



Alternatively, you might want to write an application to answer incoming calls. Basic Telephony Services provides for call establishment for either incoming or outgoing calls.

Dropping a call is a fairly obvious requirement of telephony. TAPI provides the lineDrop() function to facilitate dropping calls.

Calls move through an orderly set of states as the call is established, processed, and ultimately dropped. For outgoing calls, the states are as follows:

  Idle
  Dialtone
  Dialing
  Proceeding
  Ringback
  Connected
  Disconnected

For incoming calls, the states are as follows:

  Idle
  Offering
  Accepted
  Connected
  Disconnected

In the case of both incoming and outgoing calls, the call goes back to an idle state when disconnected. Figures 28.3 and 28.4 depict the states through which incoming and outgoing calls transition.


Figure 28.3  Outgoing call state machine.


Figure 28.4  Incoming call state machine.

TAPI provides a mechanism for monitoring and responding to the changes of state and events that are fired off by TAPI during the lifetime of a call. Your application must register a callback function through which commands and parameters are sent back to your application by means of TAPI. This model is similar to the way pre-MFC Windows applications received and handled messages. It is also similar to the way Winsock programs are notified about events associated with TCP/IP sockets.

Basic Telephony Services also provide the mechanism to retrieve and manipulate the call handle. This is important because TAPI itself provides no mechanism for transferring data over the call it establishes. You must resort to other classes and APIs to communicate (that is, transfer data) over the TAPI call. The method you use to transmit data depends on the type of data you are transferring. For message-based communications such as email or fax, the Messaging API (MAPI) would probably be most appropriate. For interactive, command-driven communication, the Win32 Communications API would be the right choice. You can use the WAVE API to send and receive audio data. For interactive voice, you would pick up the phone and converse. TAPI is used to establish and monitor calls, not transfer the data.

You will see an example of using TAPI to establish and monitor a call later in this chapter when you look at a Basic Telephony Services-based application.

Supplemental Telephony Service

The TAPI Supplemental Telephony Services are those services that are defined by the API but are not required by a particular TAPI Service Provider. The Supplemental Services go beyond basic call establishment, monitoring, and dropping. The Supplemental Services typically require additional hardware to implement. There is typically a PBX or a physical phone deskset on the other side of the Service Provider that handles the Supplemental Telephony Service request.

All TAPI Service Providers are required to support Basic Telephony Services. Implementing Supplemental Services is purely optional. For example, the Unimodem Service Provider provides a complete set of Basic Telephony Services but provides no additional Supplemental Telephony Services.

The additional functionality provided by Supplemental Telephony Services allows for implementing applications that interface to the advanced features of modern telephony systems. The services include the following:

  Call hold
  Transferring calls
  Conferencing calls
  Call forwarding
  Call parking
  Call pickup
  Call completion
  Call acceptance
  Generating and monitoring digits and tones
  Media mode monitoring
  Media stream routing and control
  Caller info
  Control over call parameters
  Phone terminal control

Several of these features are probably typical of those supplied by the PBX where you work. The call-related features are typical of user functions one might perform using a deskset. Using TAPI, an application can control the functions by using a computer application. The application might expose a GUI intended to replace or augment the deskset, providing a more user-friendly interface to the telephony system.

Additional Supplemental Telephony Services provide for monitoring calls, controlling media streams, responding to user inputs via DTMF tones, and so on. The TAPI application might implement a call desk that automates customer service requests. When a customer called, the application could prompt the caller to enter numbers to select help regarding a specific product or service. The TAPI application could control queues of calls and automate the forwarding for the calls to customer service representatives.

Additional Supplemental Telephony Services allow for control of the deskset. A modern deskset might contain an LCD display that can display alphanumeric data. A TAPI application could provide caller information via the deskset’s LCD, allowing for call screening. Or perhaps the TAPI application would use the distinctive ringer capabilities of the deskset to alert the caller that an incoming call was from the boss.

Extended Telephony Service

TAPI was designed to provide an extensible framework through which vendors can offer access to features and functions specific to their hardware solutions.

Well-written applications that use only Basic Telephony Services should run in a hardware-independent fashion, that is, they should run on anyone’s computer provided that they have a TAPI Service Provider to support the available hardware.

Well-written applications that use Supplemental Telephony Services are written in a vendor-neutral fashion. The Service Provider features they utilize are well documented by the TAPI interface. The software application should be able to run against multiple vendors’ hardware and Service Providers without being rewritten if the Supplemental Telephony Services required by the application are provided by the Service Provider(s)—that is, the application is still written in a vendor- and hardware-neutral manner.

Extended Telephony Services allow a vendor to extend the Telephony API by adding device- and Service Provider-specific features and functions. Applications written to this level of service will typically be targeted at a specific vendor’s product offering and hence will lack portability.

History of TAPI

A quick review of TAPI’s past, as well as a peek at the next version of TAPI, will help round out the discussion of how TAPI can be used to enhance applications as well as to shed some light on the importance of TAPI within the overall Windows architecture.

TAPI 1.3

TAPI started out during the days of Windows 3.1 and Windows for Workgroups 3.11 as a means of negotiating for a modem attached to a serial port. Version 1.3 was the first released version of TAPI. (Earlier beta versions spanned the 1.0 to 1.3 version space.)

TAPI 1.3 was released as a standalone SDK. TAPI 1.3 was also known as version 1.03 because it was released as the TAPI 1 SDK; which was distributed through the MSDN Level II membership. It was not a part of any OS release or any of the developer tools of the time. It included the tapi.dll, tapi.lib, and tapi.h files required in order to build 16-bit applications and Service Providers.

TAPI 1.3 is the only 16-bit-compatible version of TAPI released. If for some reason you wanted to write a TAPI application that would run on all versions of Windows, TAPI 1.3 would be your only choice. Because backward compatibility has been maintained since 1993 when TAPI first was released, it should actually be possible to do such a foolish thing! I leave it as an exercise for the reader to implement a universal TAPI client using TAPI version 1.3.

TAPI 1.3 provided Basic Telephony Services and some of the Supplemental Telephony Services. Sending data over an established call was accomplished by using the now-defunct CommXXX calls. TAPI 1.3 was certainly not the tool for creating enterprise telephony server solutions. It was, however, a good tool for implementing client applications that could dial analog modems or interface with a PBX.



TAPI 1.4

TAPI 1.4 was released along with Windows 95. It was an integral part of the operating system and required no additional distribution files such as DLLs or EXEs. TAPI 1.4 provided a client API that was 32-bit-oriented. The Service Providers continued to be 16-bit drivers, but you could write 32-bit applications with C++, VB, or any other language that could call a function in a dynamic link library.

TAPI 1.4 also increased the feature set supported by TAPI. Some of the new features included

  Plug-and-Play support
  Common dialogs for setting dialing properties
  Access to country and area code info
  Get/Set operations for application priorities
  Provider-initiated conferences
  Other capability/status extensions
  Universal modem driver

TAPI 1.5

There was a version of TAPI labeled 1.5 that was released for WinCE only. It is mentioned here only for completeness.

TAPI 2

TAPI 2 was targeted at Windows NT Server and provided a new and improved 32-bit internal architecture that replaced the 16-bit Service Providers and support services. The 2.0 infrastructure was tailored to take advantage of all the benefits of NT Server including processor independence, pre-emptive tasking, multithreading, symmetric multiprocessing, and security.

TAPI 2 provided an infrastructure capable of hosting enterprise telephony server applications. Meanwhile the world was putting more and more demands on the networking infrastructure to deliver various and specific levels of data delivery services. File transfers, for instance, required 100% guaranteed accurate delivery but could be subject to delays imposed by packet retransmissions. Streaming audio and video required timely delivery of data packets. If a packet couldn’t be delivered on time, the fact that it was accurately delivered became moot. Speech grade delivery required less than 4K bandwidth, whereas a full-bandwidth audio stream could require a 56K channel. A streaming video presentation might need to reserve 256K of bandwidth with a guaranteed latency not to exceed 500 milliseconds.

The capability of an application and/or Service Provider to specify and control the quality of service (QoS) was added to TAPI 2 as a means of allowing applications to negotiate (and renegotiate) the quality of service required by a specific TAPI call.

TAPI 2 added some additional features to make building call-center applications, such as automated help desk systems, easier to implement. These features included the addition of call queue management, call routing support, and message waiting support.

Although TAPI 2 was targeted at creating a robust TAPI infrastructure through which enterprise-class telephony server applications could be built, it was also available in the NT Workstation 4 environment. As a client platform, TAPI 2 provided backward compatibility for 16- and 32-bit TAPI applications written using TAPI 1.3 or 1.4.

Unicode support was also added to version 2. This made it easier for developers to write language-independent TAPI Service Providers and applications that could be localized by means of a resource file.

Previous versions of TAPI utilized a tapi.ini file to hold configuration information. TAPI 2 adopted use of the Registry.

TAPI 2.1

The biggest change between versions 2.0 and 2.1 was the addition of client/server functionality to the TAPI infrastructure. With version 2.1, telephony hardware could be distributed on multiple machines. Previous versions of TAPI required the hardware interface to reside on the same machine as the TAPI application that used the Service Provider. Under version 2.1, the TAPI infrastructure allowed a client application to be written that could establish calls utilizing remote resources. This was a powerful addition to the infrastructure.

The TAPI 2.1 API did not change significantly between version 2.0 and version 2.1. Indeed, the only difference in the tapi.h file associated with version 2.1 is the inclusion of a LINEMEDIAMODE_VIDEO constant that allowed for querying and opening line devices capable of processing video stream.

TAPI 2.2

Version 2.2 is the current version of TAPI. The tapi.h file provided with Visual C++ 6 defines TAPI_CURRENT_VERSION to 0x00020002 or 2.2. When you query TAPI for the version number, Windows 98 or newer installations of NT return version 2.2. But there are no version 2.2 deltas to the tapi.h header file associated with version 2.2. For all intents and purposes, version 2.2 and version 2.1 are identical from an application programmer’s vantage point.

I will review the 2.2 architecture in detail a bit later in this chapter.

TAPI 3

Let’s take a peek at what TAPI 3 promises to offer.

Computers are used for many things these days. One of the largest uses of computers is communications. For those who have worked in the industry for several years, this is not news. We have been using email, BBSs, LANs, WANs, and the Internet for quite some time now.

The emergence of the Internet has, and will continue to, change the way both business and home users communicate. The Internet is a ubiquitous amalgamation of LAN and WAN technologies. It’s cheap, reliable (arguably), and provides a wide selection of connection options in terms of price, performance, and connection options.

The Internet started as a highly reliable WAN technology that connected computers together. With time, the Internet became associated with WAIS, GOPHER, and FTP applications. Nowadays the Internet is almost synonymous with the World Wide Web and email. But the Internet is also used for Virtual Private Networks (VPNs), Electronic Data Interchange via XML, and streaming media.

The point is that the Internet has only started to change the way people use computers. The Internet is “forcing” many homes to buy computers, modem vendors to continually push the envelope on bandwidth, and cable and phone companies to deploy high-bandwidth connection options such as cable modems, ISDN, and xDSL. Satellite dishes are being employed to speed downloads over the net. Cell phone companies are working hard to build infrastructures to allow high-bandwidth digital communication over cellular modems. The future of the Internet promises much higher bandwidth connections at increasingly competitive prices. (Ain’t free market economies great!)

So what does the Internet have to do with TAPI? Well, TAPI 3 is absolutely targeted at Voice-Over-IP (VoIP). TAPI promises to allow enterprising programmers to write powerful telephony systems that route voice and digital data throughout an enterprise using IP as the backbone, either in the form of a private, secure LAN or the Internet. It’s not a giant leap to envision corporate or public phone systems that use VPNs in conjunction with TAPI and the streaming media CODECs provided by NetShow to build enterprise-wide “PBX” functionality that spans the nation or even the world.

And while you’re at it, why not throw in a video stream or two so you can do audio/video telephony with all the bells and whistles of a PBX as well? Imagine never paying any long-distance charges beyond a basic ISP monthly service charge. How about being able to videoconference from a cabin in the woods using a cell phone? Cool?

TAPI 3 is aimed at Window 2000 (formerly known as Windows NT 5). It is specifically targeted at building enterprise-level applications such as call control, interactive voice response, voice mail, call centers, and IP conferencing. The Service Providers within TAPI 3 will not only support traditional telephony mediums but will also target IP Telephony. TAPI 3 will provide a convergence of public switch telephony networks with the Internet.



TAPI 3 will move from a C API to a COM-based interface that can be easily integrated into just about any programming environment. The new COM interface will provide for call control, media stream control, and NT 5 directory services. The TAPI COM implementation will communicate with the TAPI server by means of Remote Procedure Calls (RPCs), allowing the remoting of TAPI Services. The anticipated Service Providers will include H.323 and IP Multicast support. (H.323 is an International Telecommunications Union [ITU] standard for multimedia communications [voice, video, and data] over a connectionless networking backbone, as in IP). IP Multicast is used for video confer-encing.

TAPI 3 will also interface through the Media Stream Provider Interface with Direct Show Media Streams. Direct Show takes advantage of the Real-Time Transport Protocol (RTP), which is an IETF standard designed to handle streaming audio and video delivery over the Internet.

TAPI will become the infrastructure that glues together traditional telephony infrastructure with Internet based streaming media including Voice-over-IP and video confer-encing.

So much for the future of TAPI. It’s time to look at how you can take advantage on TAPI when writing applications.

Using Assisted Telephony

As mentioned earlier, Assisted Telephony is an easy way to add phone dialer functionality to any application. Assisted Telephony requires only a single function call to be made to place an interactive phone call.

Listing 28.1 is an example of how to implement a phone dialer via Assisted Telephony.

Listing 28.1 Using Assisted Telephony


void CTapi::TestAssistedTelephony()
{
   CPhoneNum dlg;
   LONG lResult;

   lResult = dlg.DoModal();
   if ( lResult == IDOK )
   {
      lResult = tapiRequestMakeCall(
                   dlg.m_szPhoneNum,              // dest address
                   “TAPITest”,                    // appname
                   “TAPITest”,                    // caller
                   “Testing Assisted Telephony”); // comment
      if (lResult != 0L )
      {
         char szBuf[ 256 ];
         sprintf(szBuf, “tapiRequestMakeCall error - %ld”, lResult);
         ::MessageBox(NULL,szBuf,””,MB_OK);
      }
   }
}

Pretty simple, isn’t it?

The example starts by instantiating a CPhoneNum dialog. CPhoneNum is a simple class built with the ClassWizard. It prompts the user for a phone number, which is stored in a CString. DDX is used to update the public data member m_szPhoneNum. No input validation is performed. tapiRequestMakeCall() will let you know if a bad number has been entered.

The tapiRequestMakeCall() function expects four parameters. The first is the phone number that is to be called. Either a canonical address or a dialable address can be supplied. The canonical address, as you’ll recall, takes the form “+<countrycode> (<area code>) <phone number>.” A dialable address is a localized string, which might include characters required to access an outside line, a calling card number, long distance access code and area code, a pulse versus tone dial mode indicator, and so on.

The remaining three parameters are used when documenting the call in the dialer’s log file. Parameter 2 contains a pointer to a null-terminated string that contains a user-friendly application name. Parameter 3 contains a pointer to a null-terminated string that represents the called party’s name. The last parameter contains a pointer to a null-terminated string that contains a comment. You can pass NULL for any or all of the last three parameters.

tapiRequestMakeCall() returns 0 if successful. A negative number is returned if an error occurs. But success only means that the call-control application accepted the request. There is no notification that the call was successfully made.

The call-control application under Windows 95 and Windows 98 is the dialer.exe. The TAPISRV.EXE service performs the TAPI requests under NT. In response to the tapiRequestMakeCall() request, the dialer raises a dialog similar to the one shown in Figure 28.5.


Figure 28.5  The Dialing dialog box.

After the call is successfully placed, the dialog shown in Figure 28.6 is displayed for the user.


Figure 28.6  The Active Call dialog box.

If an error occurs, the user will see a dialog similar to the one in Figure 28.7.


Figure 28.7  The Line in Use dialog box.

That’s about all there is to using Assisted Telephony. It’s pretty simple, but also pretty limiting in what can be accomplished. Only interactive voice calls can be placed, and there is no way to monitor the progression of the call as it transitions through the various states an outgoing call must transition through.

Now let’s take a look at performing the same operation using Basic Telephony Services.

Using Basic Telephony

As you will recall, Basic Telephony Services provides an application with the ability to translate addresses, place calls, drop calls, monitor call state transitions, and manipulate call handles. The sample code you are going to look at next performs most of these functions.

MFC does not provide any wrapper classes for TAPI function calls. Coding TAPI is an exercise in calling C APIs. In the sample code, certain TAPI functions are wrapped in a C++ class called CTapi. CTapi is not intended to be production-quality code. Rather, it is simply a test environment used to demonstrate the steps required to make and drop a call using Basic Telephony Services. The code for the CTapi class, as well as the rest of the TAPITest application, is available on the CD-ROM distributed with the book. Feel free to enhance the CTapi class to meet your TAPI needs.

Before getting too far into looking at code, I need to define some terms that TAPI uses. Basic Telephony Services are concerned with lines, phones, addresses, and calls. A line is a logical entity that represents a physical phone line. When dealing with POTS circuits and Unimodem drivers, the physical line and logical line maintain a one-to-one relationship. Other physical infrastructures such as ISDN might provide multiple logical lines per physical line. When writing TAPI applications, you always deal with logical lines. Each logical line has characteristics that define the kinds of media it can support, the speeds it can be set to, and so on.

A line has one or more addresses associated with it. In the case of POTS circuits and Unimodem Service Providers, there is only one address associated with a specific line. But think of a typical PBX environment where a deskset has both an internal phone number and an outside direct-dial number. This is an example of two addresses being associated with the same logical line.

A logical phone represents a physical phone. The logical phone mirrors the features provided by the physical. At a minimum the logical phone has a switchhook and a transducer. A sophisticated phone might have buttons, lights, and an alphanumeric display that can be modeled by the logical phone.

The last construct is the call. A TAPI call mimics a real-world call. A call is initialized by a caller and can have many called parties associated with it. A call has a short life relative to lines, phones, and addresses. Many calls are typically placed using the same phone and line resources.

There are four basic stages that Basic Telephony sessions must be concerned with: configuration, connection, data transfer, and disconnection. Let’s look at each of these stages in detail.



Configuring TAPI

Configuration of a TAPI session starts with the lineInitialize() function. Listing 28.2 demonstrates how to call lineInitialize().

Listing 28.2 Using ::lineInitialize()


void CTapi::TestLineInitialize( void )
{
   LONG lResult;

   lResult = ::lineInitialize( &m_hTapi,
                               AfxGetInstanceHandle(),
                               TapiCallbackFunc,
                               “TAPITest”,
                               &m_dwNumLines );
   if ( lResult )
      displayTapiError( lResult );
}

The first parameter to lineInitialize() is a LPHLINEAPP, a pointer to a line application handle. The handle returned in m_hTapi becomes the identifier for TAPITest’s instance of TAPI. The handle is passed to TAPI as a parameter of most subsequent calls and consequently is stored in a public data member of the CTapi class.

The second parameter passed to lineInitialize() is an instance handle for your application. You use the AfxGetInstanceHandle() to retrieve the m_hInstance data member of the CWinApp class. TAPI requires an instance handle to identify and process events associated with your application.

The third parameter passed to lineInitialize() is a pointer to a callback function created by the application and used by TAPI to notify the application of changes in the state of the line(s) associated with the TAPI instance. The callback function needs to be declared as either FAR PASCAL or as a CALLBACK. Both evaluate the same. Win16 applications used to require that MakeProcInstance() was used to create callback pointers. Win32 applications do not require use of MakeProcInstance().

The fourth parameter to lineInitialize() is a pointer to a null-delimited string that identifies the application. Any user-friendly application name can be supplied. The name is used during logging operations. If NULL is passed, TAPI retrieves the application’s filename and uses that instead.

The final parameter to lineInitialize() is a pointer to a DWORD into which lineInitialize() stores the number of “devices” available for use by your application. As you will see, the available devices must be queried to find one that meets the application’s specific requirements.

lineInitialize() returns a LONG that specifies the results of the function call. If lineInitialize() succeeds, it returns 0. Errors are returned as negative numbers that should be interpreted through the constants supplied in TAPI.H.

Note that lineInitialize() takes no parameters to identify a particular line or a particular type of line. The actual purpose for which you call lineInitialize() is to initialize “the application’s use of Tapi.dll for subsequent use of the line abstraction.” lineInitialize() is actually more a “tapiInitialize” than a line-device initialization function.

Now that lineInitialize() has been fully explained, I need to tell you that it is an obsolete function for use with TAPI 2 and higher applications. The newer lineInitalizeEx() version requires two additional parameters: a pointer to a DWORD specifying the highest version of API version that the application was designed for and a pointer to a LINEINITIALIZEEXPARAMS structure.

Listing 28.3 shows a simple use of lineInitializeEx().

Listing 28.3 Using ::lineInitializeEx()


void CTapi::TestLineInitializeEx( void )
{
   DWORD dwVersion = 0x00020002;
   LINEINITIALIZEEXPARAMS LIP;
   LONG lResult;

   memset( &LIP, 0, sizeof( LIP ) );
   LIP.dwTotalSize = LIP.dwUsedSize = sizeof( LIP );
   LIP.dwOptions = LINEINITIALIZEEXOPTION_USEHIDDENWINDOW;

   lResult = ::lineInitializeEx( &m_hTapi,
                                 AfxGetInstanceHandle(),
                                 TapiCallbackFunc,
                                 “TAPITest”,
                                 &m_dwNumLines,
                                 &dwVersion,
                                 &LIP );
   if ( lResult )
      displayTapiError( lResult );
   m_dwAPIVersion = dwVersion;
}

There are two advantages to using the new lineInitializeEx() call. The first is the ability to negotiate the API version, and the second is the ability to control how the application is notified of events. Version negotiation will be discussed a bit later when I talk about lineNegotiateAPIVersion(). For now, suffice it to say that a desired API version can be passed to lineInitializeEx(). lineInitializeEx() determines what API level is appropriate based on installed components and returns a negotiated API version number.

The big advantage of the new lineInitializeEx() function is the ability to control the means by which the application is informed of line events. There are now three ways to receive event notifications. The first, which is the one shown in the previous example, mimics the behavior of earlier versions of TAPI. Setting the dw_Options field of the LINEINITIALIZEEXPARAMS structure to LINEINITIALIZEEXOPTION_USEHIDDENWINDOW causes TAPI to create a hidden window within the context of the TAPI application. TAPI then subclasses the hidden window so that messages posted to the window are handled by TAPI itself. When TAPI wants to post a message to the application, it posts it to the hidden window. The application then retrieves the message when it calls ::GetMessage(), which allows the TAPI WNDPROC to dispatch the message to the callback registered through the lineInitialize() function. The downside of using a hidden window is that the application must have a message loop.

TAPI 2 provides other mechanisms for event notification. If the dwOptions field is set to LINEINITIALIZEEXOPTION_USEEVENT, TAPI will create a Win32 event object that is used to signal state changes on TAPI devices. The application used the event object returned by lineInitializeEx() to call WaitForSingleObject(). When a TAPI message is available for processing, TAPI signals the event object and the blocked thread continues processing. The applications thread should call lineGetMessage() to retrieve the message. (An alternative to calling WaitForSingleObject() is to call lineGetMessage() directly. lineGetMessage() will block until TAPI has a new message available.)

The final mechanism available to TAPI applications for message notification is to create a completion port through the Win32 CreateIoCompletionPort() function. The handle to the completion port is passed to lineInitializeEx() along with the LINEINITIALIZEEXOPTION_USECOMPLETION PORT dwOptions flag. TAPI uses the Win32 PostQueuedCompletiontStatus() function to inform the application of pending messages. The application uses GetQueuedCompletionStatus() to retrieve a pointer to a LINEMESSAGE structure through which the application is informed of TAPI events.



Now that you have an initialized instance of TAPI, what comes next? The next step is to negotiate an API version that TAPI will use to communicate with the application. This is accomplished through the lineNegotiateAPIVersion() function. Listing 28.4 is an example of using lineNegotiateAPIVersion().

Listing 28.4 Using ::lineNegotiateAPIVersion()


void CTapi::TestLineNegotiateAPIVersion( void )
{
   LINEEXTENSIONID stExtensionID;
   DWORD dwAPIVersion;
   char buf[ 256 ];
   LONG lResult;

   if ( m_hTapi )
   {
      for ( DWORD line = 0; line < m_dwNumLines; line++ )
      {
         lResult = lineNegotiateAPIVersion( m_hTapi,
                                            line,
                                            0x00010003,
                                            0x00020002,
                                            &;dwAPIVersion,
                                            &;stExtensionID );
         if ( lResult )
         {
            displayTapiError( lResult );
            continue;   // try the next line...
         }
         // get info about the line
         LPLINEDEVCAPS lpLineDevCaps =
            GetDevCaps( line, dwAPIVersion );

         // generate a messagebox for info purposes
         if ((lpLineDevCaps) &&
             (lpLineDevCaps->dwLineNameSize) &&
             (lpLineDevCaps->dwLineNameOffset) &&
             (lpLineDevCaps->dwStringFormat == STRINGFORMAT_ASCII))
         {
            // This is the name of the device.
            char * lpszLineName = ((char *) lpLineDevCaps) +
                                  lpLineDevCaps->dwLineNameOffset;
            sprintf(buf,
               “Device %d - name: %s - Negotiated API version 0x%lx”,
               line, lpszLineName, dwAPIVersion);
         }
         else
         {
            sprintf(buf,
               “Device %d - no ASCII name - Negotiated API version 0x%lx”,
               line, dwAPIVersion);
         }
         ::MessageBox(NULL,buf,””,MB_OK);

         //check if the line supports voice communication
if ((lpLineDevCaps) &&
    (lpLineDevCaps->dwBearerModes & LINEBEARERMODE_VOICE) &&
    (lpLineDevCaps->dwLineFeatures & LINEFEATURE_MAKECALL) &&
    (lpLineDevCaps->dwMediaModes & LINEMEDIAMODE_INTERACTIVEVOICE))
         {
            // found a suitable line - keep it
            m_dwLineToUse = line;
            m_dwAPIVersion = dwAPIVersion;
         }

         free( lpLineDevCaps );
      }

      sprintf(buf,“Selected device %d - Negotiated API version 0x%lx”,
         m_dwLineToUse, m_dwAPIVersion );
      ::MessageBox(NULL,buf,””,MB_OK);
   }
}

lineNegotiateAPIVersion() takes six parameters. The first is the handle to the TAPI instance that was returned by lineInitialize(). The second parameter is the id for the device you want to check. The id is actually a zero-indexed subscript. The maximum value for the device ID is one less than the dwMaxDevices value returned by lineInitialize(). The third parameter is the lowest version of the Telephony API with which the application is compliant. The fourth parameter is the highest version of the Telephony API with which the application is compliant. The fifth parameter is a pointer to a DWORD in which the negotiated API version number is returned. The final parameter is a pointer to a LINEEXTENSIONID struct. Each Service Provider can implement extensions. (Remember the Extended Telephony Services?) If an application wants to utilize device-specific extensions, it can use the information returned in the LINEEXTENSIONID to interface with the Service Provider. If the application doesn’t want to use extensions, it can ignore the returned values.

When the specific API version number for a specific device id is known, it is possible to obtain additional information about the device, including what kind of media the device is capable of processing. In the previous example, a call is made to GetDevCaps(), which is a member of the CTapi class. Listing 28.5 shows the implementation of GetDevCaps().

Listing 28.5 Using ::lineGetDevCaps()


// caller must free the returned LPLINEDEVCAPS pointer
LPLINEDEVCAPS CTapi::GetDevCaps( DWORD dwDevice, DWORD dwAPIVersion )
{
   LPLINEDEVCAPS lpLineDevCaps = NULL;
   size_t dwNeeded = sizeof(LINEDEVCAPS);
   LONG lResult = 0;

   // might take two callocs to get it right...
   for ( int i = 0; i < 2; i++ )
   {
      lpLineDevCaps = (LPLINEDEVCAPS)calloc( dwNeeded,1 );
      if ( !lpLineDevCaps )
         goto error;

      // always remember to set the size in the calloc’d struct
      // before trying to use it...
      lpLineDevCaps->dwTotalSize = dwNeeded;

      lResult = ::lineGetDevCaps( m_hTapi,
                                  dwDevice,
                                  dwAPIVersion,
                                  0,
                                  lpLineDevCaps );
      if ( lResult )
      {
         displayTapiError( lResult );
         goto error;
      }

      // do we have the whole structure?
      if ( lpLineDevCaps->dwNeededSize <= lpLineDevCaps->dwTotalSize )
         break;    // yes...

      // prepare to try a second time
      dwNeeded = lpLineDevCaps->dwNeededSize;
      free( lpLineDevCaps );
      lpLineDevCaps = NULL;
   }

   return lpLineDevCaps;    // SUCCESS!

error:
   if ( lpLineDevCaps )
      free( lpLineDevCaps );
   return NULL;
}

CTapi::GetLineCaps() uses the lineGetDevCaps() to fill in a LINEDEVCAPS structure. Unfortunately it’s not as simple as allocating a LINEDEVCAPS structure on the stack and filling it. Many of TAPI’s structures start off with three variables: dwTotalSize, dwNeededSize, and dwUsedSize. These variables allow for variable-length structures that allow vendor-specific data to be appended to the end of the “structure header.” In order to retrieve a LINEDEVCAPS structure, two calls to lineGetDevCaps() are usually required. The first call retrieves the “structure header” from which the code determines the size of the full structure through the dwNeededSize member. Memory is then reallocated to allow loading of the entire structure via a second call to lineGetDevCaps().

An alternate approach would be to start off with a “worst-case” allocation and then hope that sufficient space was allocated. That might seem easier but is not the recommended technique.

You should look out for several things when accessing the LINEDEVCAPS structure (or any of Windows variable length structures). First, make sure the structure passed into Windows has the dwTotalSize parameter set. The minimum size dwTotalSize can be set to for lineGetDevCaps() to succeed is the size of the fixed portion (the “structure header”). Second, it is also a good idea to initialize the structure to zeros.

The LINEDEVCAPS structure defines the capabilities of the specific line device. Let’s look at some of the fields contained in the LINEDEVCAPS structure to see what line devices might be capable of. If you want to get information about the Service Provider associated with the line device, use the dwProviderInfoSize and dwProviderInfoOffset members to index into the LINEDEVCAPS structure. The offset variable represents the number of bytes into the structure where the data starts. The size variable is the number of bytes of data that should be retrieved. The data is not null-delimited, so you typically need to copy the bytes with strncpy() and then delimit the string yourself.



Two other variables, dwSwitchInfoSize and dwSwitchInfoOffset, describe the telephone switch to which the line device is attached. The variables dwLineNameSize and dwLineNameOffset contain index info to the logical name for the line device. This name would represent a user-friendly name possibly assigned by an administrator or user when configuring the system.

The member variable dwStringFormat defines the character set used by the line device. TAPI supports ASCII, DBCS, and Unicode strings. The Service Provider specifies the mode it supplies information in.

The dwNumAddresses field returns the number of addresses associated with this line device. There can be many addresses (phone numbers) by which a line is known. The addresses can be accessed by using an “address identifier,” which is really just an index ranging from zero to dwNumAddresses minus one.

The dwBearerMode field is particularly interesting. This variable indicates the generic type of data channel the line represents. Some of the options include LINEBEARERMODE_VOICE, LINEBEARERMODE_SPEECH, and LINEBEARERMODE_DATA.

LINEBEARERMODE_SPEECH mode represents a line capable of G.711 speech transmission. G.711 transmissions are typically subject to signal processing such as echo cancellation and compression/decompression. Such line devices are not suitable for analog modem use. Look for a LINEBEARERMODE_VOICE line instead. MULTIUSE and ALTSPEECHDATA bearer modes are usually associated with ISDN lines.

The dwMaxRate variable contains the maximum data rate at which transmissions can occur over the line as expressed in bits per second.

The dwMediaMode variable defines the media modes the line device can support. The bearer mode specifies the generic communication channel type, and the media mode specifies the types of data the communication channel can carry. Some of the values dwMediaMode can take on include LINEMEDIAMODE_INTERACTIVEVOICE, LINEMEDIAMODE_AUTOMATEDVOICE, LINEMEDIAMODE_DATAMODEM, LINEMEDIAMODE_G3FAX, LINEMEDIAMODE_G4FAX, LINEMEDIAMODE_DIGITALDATA, and LINEMEDIAMODE_VIDEOTEX.

The dwLineFeatures variable defines the capabilities of the line device to control calls. Some of the possible values are LINEFEATURE_MAKECALL, LINEFEATURE_FORWARD, and LINEFEATURE_SETTERMINAL.

There are many more variables in LINEDEVCAPS. Defining all possible values is beyond the scope of this chapter. (Entire books have been written about TAPI.) The online documentation supplied with MSVC should be referenced for a full treatment of the LINEDEVCAPS structure.

The goal of the Basic Telephony example is to dial an interactive voice call through TAPI. You have seen how to initialize a TAPI instance through lineInitialize(). The next step was to negotiate an API version through lineNegotiateAPIVersion(). After the API version was known, a call was made to lineGetDevCaps() to retrieve a LINEDEVCAPS structure so the characteristics of the line could be determined. The sample code you saw previously used the code in Listing 28.6 to ascertain if a line device was capable of making a interactive voice call.

Listing 28.6 Checking a Line’s Capabilities


//check if the line supports voice communication
if ((lpLineDevCaps) &&
    (lpLineDevCaps->dwBearerModes & LINEBEARERMODE_VOICE) &&
    (lpLineDevCaps->dwLineFeatures & LINEFEATURE_MAKECALL) &&
    (lpLineDevCaps->dwMediaModes & LINEMEDIAMODE_INTERACTIVEVOICE))
{
   // found a suitable line - keep it
   m_dwLineToUse = line;
   m_dwAPIVersion = dwAPIVersion;
}

The code used three variables of the LINEDEVCAPS structure to make the determination. First a check was made to see if the bearer mode supported LINEBEARERMODE_VOICE. Next, the dwLineFeatures variable was checked to see if the line device could initiate a call. Lastly, the dwMediaModes variable was checked to see if the line device could handle interactive voice calls.

If the line device supported all these requirements, the associated id and API version was stored for future use.

Assuming an appropriate line device has been found, the next step is to open the line through lineOpen(). Listing 28.7 shows how to do this.

Listing 28.7 Using ::lineOpen()


void CTapi::TestLineOpen( void )
{
   LONG lResult;

   lResult =
      ::lineOpen( m_hTapi,          // the app’s TAPI instance handle
                  m_dwLineToUse,    // the line to open
                  &m_hLine,         // the returned hLine
                  m_dwAPIVersion,   // from lineNegotiateAPIVersion()
                  0,                // extension version
                  (DWORD)this,      // app specific DWORD
                  LINECALLPRIVILEGE_NONE,   // outgoing calls only
                  LINEMEDIAMODE_INTERACTIVEVOICE, // dwMediaMode
                  0 ); // lpCallParams only used in LINEMAPPER mode

   if ( lResult )
      displayTapiError( lResult );
}

The first parameter to lineOpen() is the TAPI instance handle. The second parameter is the index of the line that was selected as having the right capabilities. The third parameter is a pointer to an HLINE parameter that is used to return the handle to the TAPI line instance. The fourth parameter is the API version negotiated through lineNegotiateAPIVersion() for the line you selected. The fifth variable is a DWORD that specifies the extension number the application and Service Provider agreed to operate under. Use 0 if you do not plan on using Service Provider extensions.

The sixth parameter to lineOpen is a DWORD worth of opaque data that TAPI maintains and returns to the application when the callback function registered through lineInitialize() is called. In the sample code, the this pointer of the CTapi class is passed. This allows the callback function logic to access member variables and functions associated with the HLINE that caused the callback to be invoked. (In a C implementation, the DWORD would probably contain a LPVOID that pointed to a structure that contained associated variables.)

The seventh parameter to lineOpen() is the dwPrivileges to be associated with the line device instance. The possible values include LINECALLPRIVILEGE_NONE, LINECALLPRIVILEGE_MONITOR, and LINECALLPRIVILEGE_OWNER. (There are additional LINEOPENOPTION variables that are valid under TAPI 2.x, but these are aimed at more advanced server applications. For more information, refer to the online documentation supplied with MSVC++.)

The eighth parameter to lineOpen() is the dwMediaModes settings. This parameter applies only if the dwPrivileges parameter requests LINECALLPRIVILEGE_OWNER mode. The values assigned to this parameter are the LINEMEDIAMODE_ flags seen earlier in the description of the LINEDEVCAPS structure. These flags can be bit-OR’d together to build a complete set of modes of interest to the application. Setting the LINECALLPRIVILEGE_OWNER flag in conjunction with a specific media type flag indicates that the application is requesting the ability to own incoming calls of the specified media type. TAPI will check privileges and return LINEERR_INVALPRIVSELECT in the event that some other application already has ownership. Any application can place outgoing calls; there is no ownership of outgoing call resources.

If all goes well, a return code of 0, indicating SUCCESS, is returned and the handle to the line is returned.



Connecting with TAPI

It is now time to make a call on the line device. Listing 28.8 places a call.

Listing 28.8 Using ::lineMakeCall()


void CTapi::TestLineMakeCall( CString csNumber )
{
   LPLINETRANSLATEOUTPUT lpLTO;
   char szDialStr[ 256 ];
   LONG lResult;

   lpLTO = (LPLINETRANSLATEOUTPUT)calloc( 512, 1 );
   if ( !lpLTO )
      return;

   lpLTO->dwTotalSize = 512;

   lResult = ::lineTranslateAddress( m_hTapi,
                                     m_dwLineToUse,
                                     m_dwAPIVersion,
                                     csNumber,
                                     0,      // card
                                     0,      // dwTranslateOptions
                                     lpLTO );
   if ( lpLTO->dwDialableStringSize >= 256 )
      return;


   strncpy( szDialStr,
      &((const char *)lpLTO)[ lpLTO->dwDialableStringOffset ],
      lpLTO->dwDialableStringSize );
   szDialStr[ lpLTO->dwDialableStringSize ] = ‘\0’;
   free( lpLTO );

   LINECALLPARAMS lcp;
   memset( &lcp, 0, sizeof(LINECALLPARAMS) );
   lcp.dwTotalSize = sizeof( LINECALLPARAMS );
   lcp.dwBearerMode = LINEBEARERMODE_VOICE;
   lcp.dwMediaMode = LINEMEDIAMODE_INTERACTIVEVOICE;

   lResult = ::lineMakeCall( m_hLine,     // the line’s handle
                             &m_hCall,    // returned call handle
                             szDialStr,   // the address to call
                             0,           // dwCountryCode
                             &lcp );      // call parameters

   // positive values indicate the async request was requested
   if ( lResult < 0 )
      displayTapiError( lResult );
   if ( lResult > 0 )
      m_lRequestID = lResult;
}

A dialing address must be generated before a call can be placed. This can be accomplished by calling lineTranslateAddress(), which will convert a canonical address into a localized dialing address. This was discussed earlier in the chapter.

The lineTranslateAddress() function takes several parameters. The first parameter is the TAPI instance handle you obtained from lineInitialize(). The second parameter is the device id (ordinal) for the line device you selected earlier. The third parameter is the API version you negotiated for the line device. The fourth parameter is a pointer to the canonical address (or arbitrary list of dialable digits) that is to be translated. The fifth parameter is the index for a calling card override. The sixth parameter is the dwTranslateOptions bit field that specifies any special instructions to be followed when converting the number. You can force a long distance or local address as the result of the translation. There is also a flag to request use of the calling card override specified by the fifth parameter. The final parameter is a pointer to the LINETRANSLATEOUTPUT structure the resultant address should be stored into. The application must allocate the structure and set the dwTotalSize field of the LINETRANSLATEOUTPUT structure prior to calling lineTranslateAddress().

A return value of 0 indicates that lineTranslateAddress() succeeded. The values in the LINETRANSLATEOUTPUT structure contain size and offset pairs for dialable and displayable versions of the addresses. In addition there are fields for country codes for the originator and the called party, as well as a field that identifies how the translation proceeded.

The sample application uses the dwDialableStringSize and dwDialableStringOffset to retrieve the dial string. You are now almost ready to place a call. One remaining item needs to be accomplished, which is the initialization of a LINECALLPARAMS structure that tells lineMakeCall() precisely what bearer mode and media mode this call should use. Actually, the LINECALLPARAMS structure specifies a lot of information about the desired call parameters including min and max baud rates, handshake protocols, timeouts, and so on. The default values are fine for this example, and consequently you do not need to change many things. In fact, for interactive voice calls, there is no need to supply a LINECALLPARAMS structure at all. Passing a NULL pointer will work as effectively. For any other type of call, the LINECALLPARAMS structure is required and consequently is demonstrated in the sample application.

It’s finally time to make the call. The lineMakeCall() takes six parameters. The first is the TAPI instance handle. The second is a pointer to an HCALL variable where the call handle is stored. The third parameter is the null-delimited dial string. The fourth parameter is the country code for the called party. This is essentially an override. If 0 is passed, the default country code is used. The fifth parameter is a pointer to the LINECALLPARAMS discussed previously. If lineMakeCall() fails immediately, it returns a negative number. If lineMakeCall() succeeds, it returns a positive number that represents the request id for the placed call as well as a handle for the newly placed call.

But you’re not done yet! The call handle is valid only after the callback function registered in the lineInitialize() function receives a LINE_REPLY message with a 0 (SUCCESS) status in the dwParam2 parameter. The lineMakeCall() function only requests TAPI to place a call. TAPI takes over and processes the call through each of the states required to establish the call. As the call is being set up, LINE_CALLSTATE messages are posted to the callback procedure. When the call is completed, the LINE_REPLY message is sent to the callback procedure. The dwParam1 value will contain the request id previously returned by lineMakeCall(). The dwParam2 value will contain the status of the request. A negative value in dwParam2 indicates that the call could not be successfully established. Only if the call is successfully established can the call handle returned by lineMakeCall() be used successfully.

Listing 28.9 shows how the example implements the callback procedure.

Listing 28.9 A TAPI Callback Function


void CALLBACK CTapi::TapiCallbackFunc
(
    DWORD dwDevice, DWORD dwMsg, DWORD dwCallbackInstance,
    DWORD dwParam1, DWORD dwParam2, DWORD dwParam3
)
{
   CTapi * pctapi = (CTapi *)dwCallbackInstance;


   // Handle the line messages.
   switch(dwMsg)
   {
      case LINE_CALLSTATE:
         displayCallState( dwParam1, dwParam2 );
         break;

      case LINE_CLOSE:
         if ( pctapi )
         {
            pctapi->TestLineShutdown();
         }
         break;

      case LINE_REPLY:
         if ( pctapi && (dwParam1 == pctapi->m_lRequestID ) )
         {
            if ( dwParam2 )
               displayTapiError( dwParam2 );
            else
               pctapi->m_bhCallValid = TRUE;
         }
         break;

      case LINE_CREATE:
         ::MessageBox(NULL,“Saw LINE_CREATE”,“”,MB_OK);
         break;

      default:
         OutputDebugString(“TapiCallbackFunc message ignored\n”);
         break;
   }
   return;
}

As mentioned earlier, the dwCallbackInstance parameter contains an opaque DWORD, which the sample application uses to pass the this pointer. This allows access to methods and data members of the CTapi instance that opened the line device. The logic that handles the LINE_REPLY sets the m_bhCallValid data member to TRUE if a LINE_REPLY message is received with a matching request id and a status of SUCCESS.



Transmitting Data with TAPI

Assuming that you receive the callback indicating that the requested call has been placed, you can start “transmitting data” over the line. In the case of the sample application, the data is interactive voice data. That is, someone can pick up the headset and start communicating by speaking and listening. If the call had been placed using different LINECALLPARAMS over a line that was indeed capable of a different media mode, say for instance LINEMEDIAMODE_DATAMODEM, transmitting data would entail calling Win32 function calls utilizing the modem handle retrieve through lineGetID(). The code in Listing 28.10 demonstrates how this might be accomplished.

Listing 28.10 Using ::lineGetID()


void CTapi::TestLineGetID( void )
{
   LPVARSTRING lpVarString = NULL;
   size_t dwNeeded = sizeof(VARSTRING);
   LONG lResult = 0;
   // it might take two callocs to get it right...
   for ( int i = 0; i < 2; i++ )
   {
      lpVarString = (LPVARSTRING)calloc( dwNeeded,1 );
      if ( !lpVarString )
         goto error;

      // always remember to set the size in the calloc’d struct
      // before trying to use it...
      lpVarString->dwTotalSize = dwNeeded;

      // get the modem’s handle and name
      lResult = ::lineGetID( m_hLine, 0, NULL, LINECALLSELECT_LINE,
                             lpVarString, “comm/datamodem” );

      if ( lResult )
      {
         displayTapiError( lResult );
         goto error;
      }

      // do you have the whole structure?
      if ( lpVarString->dwNeededSize <= lpVarString->dwTotalSize )
         break;   // yes...

      // prepare to try a second time
      dwNeeded = lpVarString->dwNeededSize;
      free( lpVarString );
      lpVarString = NULL;
   }

   // now retrieve the modem handle from the VarString
   m_hModem = *(LPHANDLE)(lpVarString + lpVarString->dwStringOffset);

error:
   if ( lpVarString )
      free( lpVarString );

   return;
}

Once again, the logic must work with a variable-length structure called a VarString. Two calls to calloc will be made before you actually get the desired data. The first call to calloc allocated just enough memory for the static structure header. From the header you determine the exact number of bytes required to retrieve the desired information. Assuming the second call succeeds, ::lineGetID() will have filled in the VarString with the specific data that interests you. In this case, what you want is the modem handle, which you find at the very start of the memory located at lpVarString->dwStringOffset bytes into the VarString. (The modem name actually follows the modem handle in the VarString. You could retrieve it if you were interested.) When the handle is retrieved, the memory allocated for the VarString is freed up.

The modem handle can be used with Win32 functions such as ::ReadFile(), ::WriteFile(), ::GetCommConfig(), ::GetCommState(), ::SetCommTimeouts(), and ::TransmitCommChar(), to name a few. The same technique of using ::lineGetID() can be employed to retrieve an MCI handle through which you could play MIDI files over the phone line. In this case, the “comm/datamodem” designator would be replaced with “mci/midi.” There are many device class strings that can be specified to lineGetID().

Disconnection with TAPI

After the data has been communicated across the phone line, (either by talking, using Win32 or MCI calls to transmit data, or using a proprietary protocol to transmit bytes), the next step is to hang up, or drop, the call. This is easy to accomplish with TAPI. Listing 28.11 drops an open call.

Listing 28.11 Using ::lineDrop()


void CTapi::TestLineDrop( void )
{

   LONG   lResult = 0;

   if ( m_bhCallValid && m_hCall )
      lResult = ::lineDrop( m_hCall, NULL, 0 );

   if ( lResult < 0 )
      displayTapiError( lResult );
}

The ::lineDrop() function takes three parameters. The first parameter is the handle to the existing call. Note that in the example, you use the m_bhCallValid Boolean to validate that the call handle has been “authenticated” by the callback function as described earlier in the chapter. The second parameter to ::lineDrop() is a pointer to a string that contains user-to-user information that can be sent to the remote party as part of hanging up. The example sent no data and hence passed NULL. The final parameter is the size of the user-to-user data, which is 0 in the example.

::lineDrop() returns a positive request id in similar fashion to ::lineMakeCall().

Terminating a TAPI Session

When your application is finished using TAPI, it needs to let TAPI know that it is going away. As you’ve seen, TAPI can maintain a hidden window for the application that is used to post messages to the callback function. There are other internal structures and resources allocated by TAPI on behalf of the application. In addition, the line might have been opened with a privilege of LINECALLPRIVILEGE_OWNER, which would prevent other applications from answering calls on the specific line device. For all these reasons, plus the fact that the TAPI documentation says to, you should close down the TAPI session gracefully.

Listing 28.12 demonstrates how the sample application accomplishes this.

Listing 28.12 Using ::lineShutdown()


void CTapi::TestLineShutdown( void )
{

   LONG   lResult = 0;

   TestLineDrop();

   if ( m_hTapi )
      lResult = ::lineShutdown( m_hTapi );

   if ( lResult )
      displayTapiError( lResult );

   // initialize data members
   m_hTapi = NULL;
   m_dwNumLines = 0;
   m_dwAPIVersion = 0;
   m_dwLineToUse = 0;
   m_hLine = 0;
   m_hCall = 0;
   m_bhCallValid = FALSE;
}

The TAPI ::lineShutdown() function takes the handle first given to you by ::lineInitialize() as an input. This identifies your instance to TAPI and ensures that the right one gets closed down.

Summary

This concludes this chapter’s treatment of the Telephony API. Much more could have been added and investigated. Indeed, whole books have been written about this subject. Be that as it may, you have learned about all the major topics and have seen examples of using all the high-level concepts made available through TAPI. The sample application has shown how to initialize TAPI, configure a line device, place a call, transmit data, terminate a call, and shut down the TAPI session.

TAPI 3 promises to be a powerful blending of traditional telephony and Internet technologies such as NetMeeting. TAPI will continue to grow in power and complexity.